package com.caishi.lkx.exam.service.impl;

import cn.hutool.core.util.StrUtil;
import com.caishi.lkx.common.util.IndexUtil;
import com.caishi.lkx.exam.mapper.IndustryMapper;
import com.caishi.lkx.exam.model.*;
import com.caishi.lkx.exam.service.*;
import com.caishi.lkx.exam.vo.IndexIndustryData;
import com.caishi.lkx.user.service.IGroupService;
import com.zzw.common.entity.IBSEntity;
import com.zzw.common.entity.IBaseMapper;
import com.zzw.common.entity.impl.BaseEntity;
import com.zzw.common.entity.impl.BaseIntEntity;
import com.zzw.common.service.model.base.SortModel;
import com.zzw.common.threadpool.SysThreadPool;
import com.zzw.common.utils.CommonUtil;
import com.zzw.common.utils.TimingUtil;

import com.caishi.lkx.exam.dto.UserPermissionDto;
import com.caishi.lkx.exam.ienum.type.ColumnType;

import com.caishi.lkx.exam.model.re.ExColumnConfigReModel;

import com.caishi.lkx.order.ienum.type.AssetsType;
import com.caishi.lkx.order.service.IUserAssetsDataService;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
//@DubboService(interfaceClass = IIndustryBaseService.class)
public class IndustryServiceImpl implements IIndustryService {

    @Resource
    private IndustryMapper industryMapper;

    @Resource
    private IExamService examService;
    @Resource
    private IPaperService paperService;

    @Resource
    private ICategoryService categoryService;

    @Resource
    private IChapterService chapterService;

    @Resource
    private IGroupService groupService;

    @Resource
    private IUserAssetsDataService userAssetsDataService;

    @Resource
    private IColumnService columnService;

    @Resource
    private IExColumnConfigReService exColumnConfigReService;

    @Resource
    private IExamCalendarService examCalendarService;

    public static final HashMap<String, IndexIndustryData> LOCAL_CACHE = new HashMap<>(32);

    public static final Map<String, Integer> INDEX_COUNT_CACHE = new HashMap<>();

    @Override
    public IBaseMapper<IndustryModel, String> getMapper() {
        return industryMapper;
    }

    @Override
    public String queryName(String s) {
        if (ExamModel.GJTK.equals(s)) {
            return ExamModel.GJTK_NAME;
        }
        return IIndustryService.super.queryName(s);
    }

    @Override
    public IndustryModel getById(String s) {
        if (ExamModel.GJTK.equals(s)) {
            var model = IndustryModel.builder()
                    .name(ExamModel.GJTK_NAME)
                    .build();
            model.setId(s);
            return model;
        }
        return IIndustryService.super.getById(s);
    }

    @Override
    public IndexIndustryData indexData(String industryId) {
        var cacheData = LOCAL_CACHE.get(industryId);
        if (cacheData != null) return cacheData;
        return noCacheIndexData(industryId);
    }

    @Override
    public List<IndexIndustryData> indexDataV1() {
        var cacheData = LOCAL_CACHE.get("hot");
        if (cacheData != null) return LOCAL_CACHE.values().stream().toList();
        return noCacheIndexDataV1(true);
    }

    @Override
    public List<IndustryModel> hotIndustry() {
        return selectList(wrapperProcessing(wrappers()), 2);
    }

    @Override
    public Map<String, Integer> indexCountData() {
        return INDEX_COUNT_CACHE;
    }

    @Override
    public UserPermissionDto<String> userHaveIndustry(Long localGroupId, Long userGroupId, String userId) {
        var res = new UserPermissionDto<String>();
        if (StrUtil.isBlank(userId)) return res;
        var categoryIds = userAssetsDataService.userAsset(userId, AssetsType.category)
                .stream().map(v -> Long.valueOf(v.getAssetMappingId())).distinct().toList();
        var u = categoryIds.isEmpty() ? new LinkedList<String>() : categoryService.selectBatchIds(categoryIds).stream().map(CategoryModel::getIndustryId).collect(Collectors.toList());
        res.setUser(u);
        var p = groupPermission(localGroupId, userGroupId, userId);
        if (p != null) {
            res.setGroup(p);
        }
        return res;
    }

    @Override
    public List<String> groupPermission(Long localGroupId, Long userGroupId, String userId) {
        //var examPermission = groupService.userGroupEnablePermission(localGroupId, userGroupId, userId);
//        var p = examPermission == null ? null : examPermission.stream()
//                .filter(v -> v.getType() == GroupPermission.Type.zhiye)
//                .findFirst()
//                .orElse(null);
//        if (p != null) {
//            if (CollUtil.isEmpty(p.getActionIds())) {
//                return new LinkedList<>();
//            } else {
//                return p.getActionIds();
//            }
//        }
        return null;
    }

    public IndexIndustryData noCacheIndexData(String industryId) {
        var industryData = new IndexIndustryData();
        var exams = examService.industryExam(industryId).parallelStream().map(v -> examService.indexExamData(v, true))
                .toList();
        if (exams.isEmpty()) return industryData;
        // 设置考试
        industryData.setExams(exams);
        synchronized (LOCAL_CACHE) {
            LOCAL_CACHE.put(industryId, industryData);
        }
        return industryData;
    }

    @SneakyThrows
    public List<IndexIndustryData> noCacheIndexDataV1(boolean async) {
        TimingUtil.start();
        var now = System.currentTimeMillis();
        log.info("开始首页考试缓存");
        var exams = examService.selectList(examService.wrappers()
                .select(ExamModel::getName, BaseEntity::getId, ExamModel::getIndustryId, ExamModel::getAttribute, ExamModel::getIcon, ExamModel::getServerName)
                .apply(" attribute < 4").orderByAsc(ExamModel::getSort));
        List<ExamModel> hot = new LinkedList<>();
        List<ExamModel> recommend = new LinkedList<>();
        for (var ex : exams) {
            if (ex.getIsHot()) hot.add(ex);
            if (ex.getIsRecommend()) recommend.add(ex);
        }
        var hotIndex = new IndexIndustryData();
        Callable<Integer> fr = () -> {
            hotIndex.setExams((async ? hot.parallelStream() : hot.stream()).map(v -> examService.indexExamData(v, async)).toList());
            hotIndex.setName("热门考试");
            synchronized (LOCAL_CACHE) {
                LOCAL_CACHE.put("hot", hotIndex);
            }
            return 1;
        };
        var f = async ? SysThreadPool.submit(fr) : CompletableFuture.completedFuture(fr.call());
        (async ? recommend.parallelStream() : recommend.stream()).map(v -> examService.indexExamData(v, async))
                .collect(Collectors.groupingBy(IndexIndustryData.Exam::getIndustryId))
                .forEach((k, v) -> {
                    var industry = getById(k);
                    if (industry != null) {
                        var item = new IndexIndustryData();
                        item.setIndustryId(k);
                        item.setExams(v);
                        item.setName(industry.getName());
                        item.setIcon(industry.getIcon());
                        synchronized (LOCAL_CACHE) {
                            LOCAL_CACHE.put(k, item);
                        }

                    }

                });
        f.get();
        TimingUtil.stop("首页考试缓存总时间");
        log.info("首页考试缓存总时间:{}s", (System.currentTimeMillis() - now) / 1000);
        return LOCAL_CACHE.values().stream().toList();
    }

    private List<List<String>> idSpit(List<String> inIds) {
        return IndexUtil.splitList(inIds);
    }

    @SneakyThrows
    public void indexCountDataExec(boolean async) {
        var now = System.currentTimeMillis();
        TimingUtil.start();
        log.info("开始试题数量缓存");
        var gjtkCate = categoryService.selectList(categoryService.wrappers()
                .eq(CategoryModel::getIndustryId, ExamModel.GJTK)
        ).stream().map(BaseIntEntity::getId).toList();
        var zhiyeCate = categoryService.selectList(categoryService.wrappers()
                .ne(CategoryModel::getIndustryId, ExamModel.GJTK)
        ).stream().map(BaseIntEntity::getId).toList();
        Function<List<Long>, Integer[]> work = categoryIds -> {
            if (categoryIds.isEmpty()) return new Integer[]{0, 0};
            var allColumn = columnService.selectList(columnService.wrappers()
                    .select(ColumnModel::getType, BaseEntity::getId)
                    .in(ColumnModel::getCategoryId, categoryIds)
                    .in(ColumnModel::getType, ColumnType.chapterPractice, ColumnType.paperLibrary)
            );
            var allColumnIds = allColumn.stream().map(BaseEntity::getId).toList();
            var re = exColumnConfigReService.selectList(exColumnConfigReService.wrappers()
                    .select(ExColumnConfigReModel::getConfigId, ExColumnConfigReModel::getColumnId)
                    .in(ExColumnConfigReModel::getColumnId, allColumnIds)
            );
            BiFunction<List<String>, Boolean, Integer> fun = (ids, chapter) -> {
                Map<String, Integer> idMap = ids.stream().collect(Collectors.toMap(v -> v, v -> 1, Integer::sum));
                // 找到未重复的试卷id
                var noRepeatIds = ids.stream().distinct().toList();
                if (noRepeatIds.isEmpty()) return 0;
                Map<String, Integer> map;
                if (chapter) {
                    map = idSpit(noRepeatIds).stream()
                            .flatMap(_ids -> chapterService.selectList(chapterService.wrappers().select(ChapterModel::getQcount, BaseEntity::getId)
                                    .in(BaseEntity::getId, _ids)).stream()
                            )
                            .collect(Collectors.toMap(BaseEntity::getId, ChapterModel::getQcount));
                } else {
                    map = idSpit(noRepeatIds).stream()
                            .flatMap(_ids -> paperService.selectList(paperService.wrappers().select(PaperModel::getQcount, BaseEntity::getId)
                                    .in(BaseEntity::getId, _ids)).stream()
                            )
                            .collect(Collectors.toMap(BaseEntity::getId, PaperModel::getQcount));
                }
                return idMap.entrySet().stream().mapToInt(entity -> {
                    var id = entity.getKey();
                    var qcount = map.get(id);
                    if (qcount == null) return 0;
                    return qcount * entity.getValue();
                }).sum();
            };
            var paperColumn = allColumn.stream().filter(v -> v.getType() == ColumnType.paperLibrary).map(BaseEntity::getId).toArray(String[]::new);
            var allChapterIds = re.stream().filter(v -> !CommonUtil.binarySearch(paperColumn, v.getColumnId())).map(ExColumnConfigReModel::getConfigId).toList();
            var chapterQuestionCount = fun.apply(allChapterIds, true);
            var allPaperIds = re.stream().filter(v -> CommonUtil.binarySearch(paperColumn, v.getColumnId())).map(ExColumnConfigReModel::getConfigId).toList();
            var paperQuestionCount = fun.apply(allPaperIds, false);
            return new Integer[]{chapterQuestionCount + paperQuestionCount, allPaperIds.size()};
        };

        var gjtkFuture = async ? SysThreadPool.submit(() -> work.apply(gjtkCate)) : CompletableFuture.completedFuture(work.apply(gjtkCate));
        var zhiyeData = work.apply(zhiyeCate);
        var gjtkData = gjtkFuture.get();
        INDEX_COUNT_CACHE.put("question_count", zhiyeData[0]);
        INDEX_COUNT_CACHE.put("question_count_gjtk", gjtkData[0]);
        INDEX_COUNT_CACHE.put("paper_count", zhiyeData[1]);
        INDEX_COUNT_CACHE.put("paper_count_gjtk", gjtkData[1]);
        TimingUtil.stop("试题数量缓存", true);
        log.info("试题数量缓存:{}s", (System.currentTimeMillis() - now) / 1000);
    }


    public void indexData() {
        noCacheIndexDataV1(false);
        indexCountDataExec(false);
    }

    public static <T extends Comparable<T> & Serializable> Comparator<IBSEntity<?, T>> permissionComparator(T[] perArr) {
        return (v1, v2) -> {
            var v11 = CommonUtil.binarySearch(perArr, v1.getId());
            var v21 = CommonUtil.binarySearch(perArr, v2.getId());
            if (v11 == v21 && v1 instanceof SortModel<?> v12 && v2 instanceof SortModel<?> v22) {
                return v12.getSort().compareTo(v22.getSort());
            }
            if (v11) return -1;
            return 1;
        };
    }

//    @Override
//    public List<RpcIndustryModel> rpcGetList() {
//        var lsit = industryMapper.selectList(wrappers());
//        return lsit.stream().map(v -> BeanUtil.copyProperties(v, RpcIndustryModel.class)).collect(Collectors.toList());
//    }
//
//    @Override
//    public RpcIndexExamCalendarVo rpcGetExamSubjectTotalList(String examId) {
//        ExamCalendarModel model = examCalendarService.getExamLately(examId);
//        if (model == null) {
//            model = new ExamCalendarModel();
//            model.setExamId(examId);
//        }
//        var indexExamCalendarVo = examCalendarService.tansClientVo(model, true);
//        RpcIndexExamCalendarVo calendarVo = RpcIndexExamCalendarVo.model(indexExamCalendarVo);
//        calendarVo.setIndustryId(examService.getById(examId).getIndustryId());
//        calendarVo.setCategoryId(indexExamCalendarVo.getCategoryId());
//        return calendarVo;
//    }
//
//    @Override
//    public List<RpcIndustryModel> rpcGetIndustryByIds(List<String> industryIds) {
//        if (null == industryIds || industryIds.isEmpty()) {
//            return null;
//        }
//        List<IndustryModel> industryModels = this.selectList(Wrappers.<IndustryModel>lambdaQuery()
//                .in(IndustryModel::getId, industryIds).orderByAsc(IndustryModel::getSort));
//        return industryModels.stream().map(v -> BeanUtil.copyProperties(v, RpcIndustryModel.class)).collect(Collectors.toList());
//    }
//
//    @Profile(value = {"daily", "pro"})
//    @Component
//    @AutoConfigureAfter(DiamondBeanPostProcessor.class)
//    public static class S {
//
//        private final IndustryServiceImpl industryService;
//
//        public S(IndustryServiceImpl industryService) {
//            this.industryService = industryService;
//        }
//
//        @PostConstruct
//        public void init() {
//            Stream.of(1, 2).parallel().forEach(v -> {
//                if (v == 1) {
//                    industryService.noCacheIndexDataV1(true);
//                } else {
//                    industryService.indexCountDataExec(true);
//                }
//            });
//            SysThreadPool.execute(() -> {
//                try {
//                    TimeUnit.MINUTES.sleep(10);
//                    SysThreadPool.taskScheduler.scheduleWithFixedDelay(industryService::indexData, Duration.ofMinutes(5));
//                } catch (Exception e) {
//                    SysThreadPool.taskScheduler.scheduleWithFixedDelay(industryService::indexData, Duration.ofMinutes(5));
//                }
//            });
//        }
//    }

}
